package cn.pangolin.pro.framework.common.util.io;

import com.google.zxing.BarcodeFormat;
import com.google.zxing.EncodeHintType;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.qrcode.QRCodeWriter;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;
import lombok.SneakyThrows;
import org.apache.commons.fileupload.disk.DiskFileItem;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;
import sun.font.FontDesignMetrics;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.font.LineMetrics;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.*;
import java.net.URL;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.Objects;

/**
 * 二维码生成工具类
 *
 * @author Rex
 * @since 2024/8/1 15:31
 */
public class QRCodeUtils {

    // 二维码尺寸，宽度和高度均是320
    private static final int QRCODE_SIZE = 320;
    // 二维码图片类型
    private static final String FORMAT_TYPE = "PNG";

    /**
     * 默认需要logo,无底部文字
     * 返回 BufferedImage 可以使用ImageIO.write(BufferedImage, "png", outputStream);输出
     *
     * @param dataStr 二维码信息
     * @return {@link BufferedImage}
     * @author Rex
     * @since 2024/8/1 15:33
     */
    @SneakyThrows
    public static BufferedImage getQRCodeImage(String dataStr) {
        return getQRCodeImage(dataStr, true, null);
    }

    /**
     * 默认需要logo,无底部文字
     *
     * @param dataStr 二维码信息
     * @return {@link byte[]}
     * @author Rex
     * @since 2024/8/1 15:39
     */
    @SneakyThrows
    public static byte[] getQRCodeByte(String dataStr) {
        BufferedImage bufferedImage = getQRCodeImage(dataStr, true, null);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(bufferedImage, FORMAT_TYPE, outputStream);
        return outputStream.toByteArray();
    }

    /**
     * 默认需要logo，包含底部文字 文字为空则不显示文字
     *
     * @param dataStr    二维码信息
     * @param bottomText 底部文字
     * @return {@link BufferedImage} 可以使用ImageIO.write(BufferedImage, "png", outputStream);输出
     * @author Rex
     * @since 2024/8/1 15:53
     */
    @SneakyThrows
    public static BufferedImage getQRCodeImage(String dataStr, String bottomText) {
        return getQRCodeImage(dataStr, true, bottomText);
    }

    /**
     * 默认需要logo，包含底部文字 文字为空则不显示文字
     *
     * @param dataStr    二维码信息
     * @param bottomText 底部文字
     * @return {@link byte[]}
     * @author Rex
     * @since 2024/8/1 15:54
     */
    @SneakyThrows
    public static byte[] getQRCodeByte(String dataStr, String bottomText) {
        BufferedImage bufferedImage = getQRCodeImage(dataStr, true, bottomText);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        ImageIO.write(bufferedImage, FORMAT_TYPE, outputStream);
        return outputStream.toByteArray();
    }

    /**
     * 获取二维码图片
     *
     * @param dataStr    二维码内容
     * @param needLogo   是否需要添加logo
     * @param bottomText 底部文字       为空则不显示
     * @return {@link BufferedImage}
     * @author Rex
     * @since 2024/8/1 15:59
     */
    @SneakyThrows
    public static BufferedImage getQRCodeImage(String dataStr, boolean needLogo, String bottomText) {
        return getQRCodeImage(dataStr, needLogo, null, bottomText, 30);
    }

    /**
     * 获取二维码图片
     *
     * @param dataStr    二维码内容
     * @param needLogo   是否需要添加logo
     * @param logo       logo
     * @param bottomText 底部文字       为空则不显示
     * @return {@link BufferedImage}
     * @author Rex
     * @since 2024/8/1 15:59
     */
    @SneakyThrows
    public static BufferedImage getQRCodeImage(String dataStr, boolean needLogo, String logo, String bottomText, int logoSize) {
        if (dataStr == null) {
            throw new RuntimeException("未包含任何信息");
        }
        HashMap<EncodeHintType, Object> hints = new HashMap<>();
        hints.put(EncodeHintType.CHARACTER_SET, "utf-8");    //定义内容字符集的编码
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L);        //定义纠错等级
        hints.put(EncodeHintType.MARGIN, 1);
        QRCodeWriter qrCodeWriter = new QRCodeWriter();
        BitMatrix bitMatrix = qrCodeWriter.encode(dataStr, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE, hints);

        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        int tempHeight = height;
        if (StringUtils.hasText(bottomText)) {
            tempHeight = tempHeight + 12;
        }
        BufferedImage image = new BufferedImage(width, tempHeight, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        // 判断是否添加logo
        if (needLogo) {
            insertLogoImage(image, logo, logoSize);
        }
        // 判断是否添加底部文字
        if (StringUtils.hasText(bottomText)) {
            addFontImage(image, bottomText);
        }
        ImageIO.write(image, "PNG", new File("./custom_qrcode.png"));
        // 上传到文件中心
        return image;
    }

    /**
     * 插入logo图片
     *
     * @param source 二维码图片
     * @author Rex
     * @since 2024/8/1 16:00
     */
    private static void insertLogoImage(BufferedImage source, String logo, int logoSize) throws Exception {
        InputStream inputStream;
        if (Objects.isNull(logo)) {
            // 默认logo放于resource/static/image目录下
            ClassPathResource classPathResource = new ClassPathResource("static/image/logo.png");
            inputStream = classPathResource.getInputStream();
            if (inputStream.available() == 0) {
                return;
            }
        } else {
            inputStream = loadInputStreamFromURL(logo);
        }
        Image src = ImageIO.read(inputStream);

        Image image = src.getScaledInstance(logoSize, logoSize, Image.SCALE_SMOOTH);
        BufferedImage tag = new BufferedImage(logoSize, logoSize, BufferedImage.TYPE_INT_RGB);
        Graphics g = tag.getGraphics();
        g.drawImage(image, 0, 0, null); // 绘制缩小后的图
        g.dispose();
        src = image;

        // 插入LOGO
        Graphics2D graph = source.createGraphics();
        int x = (QRCODE_SIZE - logoSize) / 2;
        int y = (QRCODE_SIZE - logoSize) / 2;
        graph.drawImage(src, x, y, logoSize, logoSize, null);
        Shape shape = new RoundRectangle2D.Float(x, y, logoSize, logoSize, 6, 6);
        graph.setStroke(new BasicStroke(3f));
        graph.draw(shape);
        graph.dispose();
    }

    /**
     * 图片添加文字
     *
     * @param source      二维码图片
     * @param declareText 底部文字
     * @author Rex
     * @since 2024/8/1 16:02
     */
    private static void addFontImage(BufferedImage source, String declareText) {
        //生成image
        int defineWidth = QRCODE_SIZE;
        int defineHeight = 20;
        BufferedImage textImage = new BufferedImage(defineWidth, defineHeight, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = (Graphics2D) textImage.getGraphics();
        //开启文字抗锯齿
        g2.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g2.setBackground(Color.WHITE);
        g2.clearRect(0, 0, defineWidth, defineHeight);
        g2.setPaint(Color.BLACK);
        FontRenderContext context = g2.getFontRenderContext();
        //部署linux需要注意 linux无此字体会显示方块
        Font font = new Font("宋体", Font.BOLD, 15);
        g2.setFont(font);
        LineMetrics lineMetrics = font.getLineMetrics(declareText, context);
        FontMetrics fontMetrics = FontDesignMetrics.getMetrics(font);
        float offset = (defineWidth - fontMetrics.stringWidth(declareText)) / 2;
        float y = (defineHeight + lineMetrics.getAscent() - lineMetrics.getDescent() - lineMetrics.getLeading()) / 2;
        g2.drawString(declareText, (int) offset, (int) y);

        Graphics2D graph = source.createGraphics();
        //开启文字抗锯齿
        graph.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        //添加image
        int width = textImage.getWidth(null);
        int height = textImage.getHeight(null);

        graph.drawImage(textImage, 0, QRCODE_SIZE - 8, width, height, Color.WHITE, null);
        graph.dispose();
    }

    /**
     * 根据网络图片获取文件输入流
     *
     * @param imageURL 网络图片路径
     * @return {@link InputStream}
     * @author Rex
     * @since 2024/8/1 17:29
     */
    public static InputStream loadInputStreamFromURL(String imageURL) {
        try {
            // 创建一个URL对象
            URL url = new URL(imageURL);
            // 通过URL对象的openStream()方法获取图片的输入流
            return url.openStream();
        } catch (IOException e) {
            // 处理异常，例如打印错误日志或抛出运行时异常
            e.printStackTrace();
            return null; // 或者根据需求抛出异常
        }
    }

    /**
     * BufferedImage 2 byte[]
     *
     * @param image      图片
     * @param formatName 格式化名称，png,jpeg等
     * @return {@link byte[]}
     * @author Rex
     * @since 2024/8/1 17:29
     */
    public static byte[] bufferedImageToBytes(BufferedImage image, String formatName) {
        ByteArrayOutputStream os = new ByteArrayOutputStream();
        try {
            ImageIO.write(image, formatName, os);
            os.flush();
            byte[] imageInByte = os.toByteArray();
            os.close();
            return imageInByte;
        } catch (IOException e) {
            // Handle the exception
            e.printStackTrace();
            return null;
        }
    }

    public static MultipartFile bufferedImageToMultipartFile(BufferedImage image, String formatName, String fileName) throws Exception {
        // 使用ByteArrayOutputStream将BufferedImage写入到一个字节流中
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        ImageIO.write(image, formatName, baos); // 或者使用其他格式，如"jpg"

        // 创建一个临时文件来保存这个字节流
        File tempFile = File.createTempFile(fileName, ".png"); // 或者使用其他扩展名
        try (FileOutputStream fos = new FileOutputStream(tempFile)) {
            fos.write(baos.toByteArray());
        }

        DiskFileItem dfi = new DiskFileItem(fileName, "image/" + formatName, false, fileName, 0, new File("./temp." + fileName));
        MultipartFile multipartFile = new CommonsMultipartFile(dfi);
        return multipartFile;
    }


}

class CommonsMultipartFile implements MultipartFile {

    private final DiskFileItem diskFileItem;

    public CommonsMultipartFile(DiskFileItem diskFileItem) {
        this.diskFileItem = diskFileItem;
    }

    @Override
    public String getName() {
        return diskFileItem.getName();
    }

    @Override
    public String getOriginalFilename() {
        return diskFileItem.getName();
    }

    @Override
    public String getContentType() {
        return diskFileItem.getContentType();
    }

    @Override
    public boolean isEmpty() {
        return diskFileItem.getSize() == 0;
    }

    @Override
    public long getSize() {
        return diskFileItem.getSize();
    }

    @Override
    public byte[] getBytes() {
        return diskFileItem.get();
    }

    @Override
    public InputStream getInputStream() throws IOException {
        return diskFileItem.getInputStream();
    }

    @Override
    public Resource getResource() {
        return MultipartFile.super.getResource();
    }

    @Override
    public void transferTo(File dest) throws IOException, IllegalStateException {

    }

    @Override
    public void transferTo(Path dest) throws IOException, IllegalStateException {
        MultipartFile.super.transferTo(dest);
    }
}

